/*
 * ClassHierarchyAwareMap.java
 *
 * Copyright 2010 Jack Leow
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package my.edu.clhs.stringtemplate.renderer;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * {@link Map} implementation whose keys are aware of class hierarchies.
 * When looking up a key that represents a Java class, it will match not only
 * the class itself, but any of the class' ancestors in the class (but not
 * interface) hierarchy as well.
 *
 * @author Jack Leow
 * @version 1.0
 * @since 1.0 (May 15, 2010, 10:14:50 PM)
 */
public class ClassHierarchyAwareMap implements Map {
    private final HashMap delegatee = new HashMap();
    
    /**
     * In addition to the default {@link Map#containsKey(Object)} behavior, also
     * returns <tt>true</tt> if the specified key represents a Java class and
     * any of its ancestors are mapped in this map.
     */
    public boolean containsKey(Object key) {
        boolean contains = delegatee.containsKey(key);
        if (!contains && key instanceof Class) {
            Class superClass = ((Class)key).getSuperclass();
            if (superClass != null) {
                contains = containsKey(superClass);
            }
        }
        
        return contains;
    }
    
    /**
     * In addition to the default {@link Map#get(Object)} behavior, if the
     * specified key represents a Java class, also returns the value mapped for
     * its ancestors.
     * The value mapped for the closest ancestor (i.e., the first class mapped
     * when traversing up the hierarchy tree) will always be returned when there
     * is more than one such mapping.
     * 
     * <p>For example, given a <tt>ClassHierarchyAwareMap</tt> with keys
     * {@link java.lang.Throwable}.class and {@link java.lang.Exception}.class:
     * <table border="1">
     *  <tr>
     *    <th>Passing In</th>
     *    <th>Returns The Value Mapped For</th>
     *    <th>Notes</th>
     *  </tr>
     *  <tr>
     *    <td><tt>java.lang.Throwable.class</tt></td>
     *    <td><tt>java.lang.Throwable.class</tt></td>
     *    <td>Exact match.</td>
     *  </tr>
     *  <tr>
     *    <td><tt>java.lang.Exception.class</tt></td>
     *    <td><tt>java.lang.Exception.class</tt></td>
     *    <td>Exact match.</td>
     *  </tr>
     *  <tr>
     *    <td><tt>java.lang.Error.class</tt></td>
     *    <td><tt>java.lang.Throwable.class</tt></td>
     *    <td>Only ancestor.</td>
     *  </tr>
     *  <tr>
     *    <td><tt>java.lang.NullPointerException.class</tt></td>
     *    <td><tt>java.lang.Exception.class</tt></td>
     *    <td>
     *      <tt>Throwable</tt> and <tt>Exception</tt> are both ancestors, but
     *      <tt>Exception</tt> is a closer ancestor (parent's parent) than
     *      <tt>Throwable</tt> (parent's parent's parent).
     *    </td>
     *  </tr>
     *  <tr>
     *    <td><tt>java.lang.Object.class</tt></td>
     *    <td><tt>(returns <tt>null</tt>)</td>
     *    <td>
     *      <tt>Object</tt> is an ancestor of <tt>Throwable</tt> and
     *      <tt>Exception</tt>.
     *    </td>
     *  </tr>
     *  <tr>
     *    <td><tt>java.lang.String.class</tt></td>
     *    <td><tt>(returns <tt>null</tt>)</td>
     *    <td>
     *      <tt>String</tt> is not a descendent of <tt>Throwable</tt> or
     *      <tt>Exception</tt>.
     *    </td>
     *  </tr>
     * </table>
     * </p>
     */
    public Object get(Object key) {
        Object value = delegatee.get(key);
        if (value == null && key instanceof Class) {
            Class superClass = ((Class)key).getSuperclass();
            if (superClass != null) {
                value = get(superClass);
            }
        }
        
        return value;
    }
    
    // Only delegate methods beyond this line
    /**
     * Delegated to internal {@link Map}.
     */
    public void clear() {
        delegatee.clear();
    }
    
    /**
     * Delegated to internal {@link Map}.
     */
    public boolean containsValue(Object value) {
        return delegatee.containsValue(value);
    }
    
    /**
     * Delegated to internal {@link Map}.
     */
    public Set entrySet() {
        return delegatee.entrySet();
    }
    
    /**
     * Delegated to internal {@link Map}.
     */
    public boolean equals(Object o) {
        return delegatee.equals(o);
    }
    
    /**
     * Delegated to internal {@link Map}.
     */
    public int hashCode() {
        return delegatee.hashCode();
    }
    
    /**
     * Delegated to internal {@link Map}.
     */
    public boolean isEmpty() {
        return delegatee.isEmpty();
    }
    
    /**
     * Delegated to internal {@link Map}.
     */
    public Set keySet() {
        return delegatee.keySet();
    }
    
    /**
     * Delegated to internal {@link Map}.
     */
    public Object put(Object key, Object value) {
        return delegatee.put(key, value);
    }
    
    /**
     * Delegated to internal {@link Map}.
     */
    public void putAll(Map t) {
        delegatee.putAll(t);
    }
    
    /**
     * Delegated to internal {@link Map}.
     */
    public Object remove(Object key) {
        return delegatee.remove(key);
    }
    
    /**
     * Delegated to internal {@link Map}.
     */
    public int size() {
        return delegatee.size();
    }
    
    /**
     * Delegated to internal {@link Map}.
     */
    public String toString() {
        return delegatee.toString();
    }
    
    /**
     * Delegated to internal {@link Map}.
     */
    public Collection values() {
        return delegatee.values();
    }
}